package com.yeshine.opendata.utils.mybatis;

import com.yeshine.opendata.utils.PageResult;
import com.yeshine.opendata.utils.ReflectUtil;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;

import java.sql.*;
import java.util.Properties;

/**
 * Created by taohj on 2014/4/17.
 * 不在拦截器中计算总数，影响效率
 * <p/>
 * 分页拦截器，用于拦截需要进行分页查询的操作，然后对其进行分页处理。 利用拦截器实现Mybatis分页的原理：
 * 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象，而且对应的Sql语句
 * 是在Statement之前产生的，所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。
 * <p/>
 * 在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。
 * 所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法 ，然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句，
 * 之后再调用StatementHandler对象的prepare方法，即调用invocation.proceed()。
 * 对于分页而言，在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少，这是通过获取到了原始的Sql语句后，把它改为对应的统计语句再利用Mybatis封装好的参数和设
 * 置参数的功能把Sql语句中的参数进行替换，之后再执行查询记录数的Sql语句进行总记录数的统计。
 */
@Intercepts({@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class})})
public class MybatisPageInterceptor implements Interceptor {

    Logger logger = Logger.getLogger(getClass());


    // 分页帮助类，不同的数据库有不同的帮助类
    public MybatisPageHelpper pageHelpper;


    public void setPageHelpper(MybatisPageHelpper pageHelpper) {
        this.pageHelpper = pageHelpper;
    }


    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        /**
         * PreparedStatement
         * mappedStatement--
         * BoundSql ---
         * parameterObject
         * configuration
         * */


        //获得boundSql
        final RoutingStatementHandler routingStatementHandler = (RoutingStatementHandler) invocation.getTarget();
        final StatementHandler statementHandler = (StatementHandler) ReflectUtil.getFieldValue(routingStatementHandler, "delegate");
        final BoundSql boundSql = statementHandler.getBoundSql();

        //获得 MappedStatement
        PreparedStatementHandler preparedStatementHandler = (PreparedStatementHandler) ReflectUtil.getFieldValue((invocation.getTarget()), "delegate");
        MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(preparedStatementHandler, "mappedStatement");

        // 拿到当前绑定Sql的参数对象，就是我们在调用对应的Mapper映射语句时所传入的参数对象
        final Object parameterObject = boundSql.getParameterObject();


        // 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
        if (parameterObject instanceof PageResult) {

            PageResult pageResult = (PageResult) parameterObject;
            pageHelpper.setPageResult(pageResult);
            StringBuffer sql = new StringBuffer(boundSql.getSql());


            Connection connection = (Connection) invocation.getArgs()[0];

            this.setPageResultTotal(pageResult, connection, boundSql);


            if (StringUtils.hasLength(pageResult.getWhere())){
                sql.append(" "+pageResult.getWhere()+" ");
            }

            //自定义排序
            if (StringUtils.hasLength(pageResult.getSortColumn()) && StringUtils.hasLength(pageResult.getSortBy())) {
                sql.append(" order by ").append(pageResult.getSortColumn()).append(" ").append(pageResult.getSortBy());
            }

            //获得分页sql语句
            final String pageSql = pageHelpper.getPageSql(sql, mappedStatement.getConfiguration().getDatabaseId());
            // 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
            ReflectUtil.setFieldValue(boundSql, "sql", pageSql);


        }

        System.out.println("--------------------->");
        System.out.println(boundSql.getSql());
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {


    }


    private void setPageResultTotal(PageResult pageResult, Connection connection, BoundSql boundSql) {


        StringBuffer sql = new StringBuffer(boundSql.getSql());

        if (StringUtils.hasLength(pageResult.getWhere())){
            sql.append(" "+pageResult.getWhere()+" ");
        }

        sql.insert(0, "select count(*) total from  ( ").append(" )  cnt ");
       /* System.out.println("countSql:----------------=>");
        System.out.println(sql.toString());*/
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            preparedStatement = connection.prepareStatement(sql.toString());
            setParameters(preparedStatement,boundSql);
            resultSet = preparedStatement.executeQuery();
            System.out.println(resultSet.next());
            ;


            Object o = resultSet.getObject("total");
            System.out.println(o);
            int total = resultSet.getInt("total");
            pageResult.setTotal(total);

            System.out.println(resultSet.next());
            ;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                //  if (connection!=null){connection.close();}
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }





    /**
     *
     *
     * @preparedStatement
     * @parmeterMappings
     * @parameterObject
     * @throws SQLException
     * 对SQL参数(?)设值,参考DefaultParameterHandler
     */


    @SuppressWarnings(value = "unchecked")
    protected void setParameters(PreparedStatement preparedStatement,BoundSql boundSql) throws SQLException {
        for (int i = 0; i<boundSql.getParameterMappings().size() ; i++) {
            ParameterMapping parmentMapping=boundSql.getParameterMappings().get(i);
            String propertyName=parmentMapping.getProperty();
            Object value=boundSql.getAdditionalParameter(propertyName);
            TypeHandler typeHandler = parmentMapping.getTypeHandler();
            if (typeHandler == null) {
                throw new ExecutorException("There was no TypeHandler found for parameter "+ propertyName + "");
            }
            JdbcType jdbctype=parmentMapping.getJdbcType();
            if (jdbctype==null){
                jdbctype=JdbcType.forCode(Types.VARCHAR);
            }
            typeHandler.setParameter(preparedStatement, i + 1, value, jdbctype);
        }

    }
}
